package cn.geminis.crypto.csp.soft.gm;

import cn.geminis.crypto.core.util.ObjectUtils;
import cn.geminis.crypto.csp.soft.AbstractSoftDigest;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.util.Pack;

import java.util.Arrays;

/**
 * @author Allen
 */
public class Sm3Digest extends AbstractSoftDigest {

    @Override
    protected Digest createDigest() {
        return new org.bouncycastle.crypto.digests.SM3Digest();
    }

    @Override
    public String getAlgorithmOid() {
        return GMObjectIdentifiers.sm3.getId();
    }

    @Override
    public void setEncodedState(byte[] data) {
        this.digest.reset();

        var values = new int[3][];
        int[][] valueSize = {{0, 8}, {32, 16}, {96, 1}};
        for (var i = 0; i < values.length; i++) {
            var valueData = Arrays.copyOfRange(data, valueSize[i][0], valueSize[i][0] + valueSize[i][1] * 4);
            var value = new int[valueSize[i][1]];
            for (var j = 0; j < value.length; j++) {
                value[j] = Pack.bigEndianToInt(valueData, j * 4);
            }
            values[i] = value;
        }

        var xBuf = Arrays.copyOfRange(data, 100, 104);
        var xBufOff = Pack.bigEndianToInt(data, 104);
        var byteCount = Pack.bigEndianToLong(data, 108);

        ObjectUtils.setValue(this.digest, "V", values[0]);
        ObjectUtils.setValue(this.digest, "inwords", values[1]);
        ObjectUtils.setValue(this.digest, "xOff", values[2][0]);
        ObjectUtils.setValue(this.digest, "xBuf", xBuf);
        ObjectUtils.setValue(this.digest, "xBufOff", xBufOff);
        ObjectUtils.setValue(this.digest, "byteCount", byteCount);
    }

    @Override
    public byte[] getEncodedState() {
        var state = new byte[(8 + 16 + 1) * 4 + 4 + 4 + 8];
        var v = (int[]) ObjectUtils.getValue(this.digest, "V");
        var inwords = (int[]) ObjectUtils.getValue(this.digest, "inwords");
        var xOff = (int) ObjectUtils.getValue(this.digest, "xOff");
        var xBuf = (byte[]) ObjectUtils.getValue(this.digest, "xBuf");
        var xBufOff = (int) ObjectUtils.getValue(this.digest, "xBufOff");
        var byteCount = (long) ObjectUtils.getValue(this.digest, "byteCount");

        int[] index = {0};
        var values = new int[][]{v, inwords, new int[]{xOff}};
        Arrays.stream(values).forEach(val -> Arrays.stream(val).forEach(value -> {
            Pack.intToBigEndian(value, state, index[0]);
            index[0] += 4;
        }));

        System.arraycopy(xBuf, 0, state, 100, 4);
        Pack.intToBigEndian(xBufOff, state, 104);
        Pack.longToBigEndian(byteCount, state, 108);

        return state;
    }

}
